import type {AxiosInstance, InternalAxiosRequestConfig} from 'axios'
import axios from 'axios'
import type {RequestConfig, RequestInterceptors} from '@/api/interface/type.ts'
import {ElLoading, ElMessage} from 'element-plus'
import {type LoadingInstance} from 'element-plus/lib/components/loading/src/loading'
import {Decrypt, Encrypt, encryptedData, getRandomKeyIv, getSign} from '@/utils/crypto.ts'
import {v4 as uuidv4} from 'uuid'
import useUserStore from '@/store/user.ts'
import Api from '@/api'

const DEFAULT_LOADING = true
const decryptReqList = {}

class Request {
	instance: AxiosInstance
	interceptors?: RequestInterceptors
	loading?: LoadingInstance
	showLoading: boolean
	
	constructor(config: RequestConfig) {
		this.instance = axios.create(config)
		this.interceptors = config.interceptors
		this.showLoading = config.showLoading ?? DEFAULT_LOADING
		
		// 从config中取出的拦截器是对应实例的拦截器
		this.instance.interceptors.request.use(
			this.interceptors?.requestInterceptors,
			this.interceptors?.requestInterceptorsCatch
		)
		this.instance.interceptors.response.use(
			this.interceptors?.responseInterceptors,
			this.interceptors?.responseInterceptorsCatch
		)
		// 全局请求拦截器
		this.instance.interceptors.request.use(config => {
			if (this.showLoading) {
				this.loading = ElLoading.service({
					lock: true,
					background: 'rgba(244,243,243,0.3)',
					text: 'Loading'
				})
			}
			if (import.meta.env.VITE_ENCIPHER === 'true') {
				let obj = {
					key: getRandomKeyIv(),
					iv: getRandomKeyIv()
				}
				const appId = encryptedData(obj)
				const nonce = uuidv4()
				const timestamp = new Date().getTime()
				config.headers.appId = appId
				config.headers.nonce = nonce
				config.headers.timestamp = timestamp
				config.headers.sign = getSign(appId, nonce, timestamp, config.data)
				decryptReqList[config.headers.sign] = obj
				if (config.data) config.data = {data: Encrypt(config.data, obj.key, obj.iv)}
			}
			return config
		}, error => {
			return Promise.reject(error)
		})
		// 全局响应拦截器
		this.instance.interceptors.response.use(async res => {
			setTimeout(() => {
				this.loading?.close()
			}, 500)
			if (res.data.code === 200) {
				if (import.meta.env.VITE_ENCIPHER === 'true') {
					let key = decryptReqList[res.config.headers.sign].key
					let iv = decryptReqList[res.config.headers.sign].iv
					res.data.data = res.data.data ? Decrypt(res.data.data, key, iv) : res.data.data
				}
			} else if (res.data.code === 401 && !res.config.headers?.refreshToken) {//返回401并且不是刷新的接口
				const resp = await refreshTokenHandle()
				if (resp.code === 200) {
					useUserStore().setToken(resp.data.accessToken)
					useUserStore().setFreshToken(resp.data.refreshToken)
					res.config.headers.Authorization = resp.data.accessToken
					res.config.data = JSON.parse(res.config.data)
					res = await this.instance.request(res.config)
				} else {
					useUserStore().userLogout()
					setTimeout(() => {
						location.href = '/login'
					}, 1000)
				}
				return res
			} else if (res.data.code === 40103) { // 账号在其他地方登录
				ElMessage.error(res.data[config.msgField || 'msg'])
				useUserStore().userLogout()
				setTimeout(() => {
					location.href = '/login'
				}, 1000)
			} else {
				ElMessage.error(res.data[config.msgField || 'msg'])
			}
			return res.data
		}, error => {
			setTimeout(() => {
				this.loading?.close()
			}, 500)
			ElMessage.error(error.message)
			return Promise.reject(error)
		})
	}
	
	async request<T>(config: RequestConfig): Promise<T> {
		return await new Promise((resolve, reject) => {
			// 单个请求对config的处理
			if (config.interceptors?.requestInterceptors) {
				config = config.interceptors.requestInterceptors((config as InternalAxiosRequestConfig))
			}
			
			// 判断是否需要显示loading
			if (config.showLoading === false) {
				this.showLoading = config.showLoading
			}
			
			this.instance.request<any, T>(config).then((res: T) => {
				// 单个请求对数据的处理
				if (config.interceptors?.responseInterceptors) {
					res = config.interceptors.responseInterceptors(res)
				}
				this.showLoading = DEFAULT_LOADING
				resolve(res)
			}).catch(err => {
				this.showLoading = DEFAULT_LOADING
				reject(err)
			})
		})
	}
	
	async get<T>(config: RequestConfig): Promise<T> {
		return await this.request({...config, method: 'GET'})
	}
	
	async post<T>(config: RequestConfig): Promise<T> {
		return await this.request({...config, method: 'POST'})
	}
	
	async delete<T>(config: RequestConfig): Promise<T> {
		return await this.request({...config, method: 'DELETE'})
	}
	
	async patch<T>(config: RequestConfig): Promise<T> {
		return await this.request({...config, method: 'PATCH'})
	}
	
	async put<T>(config: RequestConfig): Promise<T> {
		return await this.request({...config, method: 'PUT'})
	}
}

let promise: any
const refreshTokenHandle = async () => {
	if (promise) {
		return promise
	}
	promise = new Promise(async (resolve) => {
		const resp = await Api.refreshTokens()
		resolve(resp)
	})
	promise.finally(() => {
		promise = null
	})
	return promise
}

export default Request
